package com.wzs.rpc.proxy;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.wzs.rpc.RpcApplication;
import com.wzs.rpc.config.RpcConfig;
import com.wzs.rpc.constant.RpcConstant;
import com.wzs.rpc.fault.retry.RetryStrategy;
import com.wzs.rpc.fault.retry.RetryStrategyFactory;
import com.wzs.rpc.fault.tolerant.TolerantStrategy;
import com.wzs.rpc.fault.tolerant.TolerantStrategyFactory;
import com.wzs.rpc.loadbalancer.LoadBalancer;
import com.wzs.rpc.loadbalancer.LoadBalancerFactory;
import com.wzs.rpc.model.RpcRequest;
import com.wzs.rpc.model.RpcResponse;
import com.wzs.rpc.model.ServiceMetaInfo;
import com.wzs.rpc.protocol.*;
import com.wzs.rpc.registry.Registry;
import com.wzs.rpc.registry.RegistryFactory;
import com.wzs.rpc.serializer.JDKSerializer;
import com.wzs.rpc.serializer.Serializer;
import com.wzs.rpc.serializer.SerializerFactory;
import com.wzs.rpc.server.tcp.VertxTcpClient;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetSocket;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CompletableFuture;

@Slf4j
public class ServiceProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcConfig rpcConfig = RpcApplication.getRpcConfig();
        ProtocolMessageSerializerEnum serializerEnum = ProtocolMessageSerializerEnum.getEnumByName(rpcConfig.getSerializer());
        if (serializerEnum==null) {
            log.error("序列化器不存在");
            throw new RuntimeException("序列化器不存在");
        }
        log.debug("ServiceProxy使用的序列化器为 {}", serializerEnum.getName());
        //构造RpcRequest
        String serviceName = method.getDeclaringClass().getName();
        RpcRequest rpcRequest = RpcRequest.builder()
                .serviceName(serviceName)
                //@Builder注解不会将字段的默认值包含在构建器中
                .serviceVersion(RpcConstant.DEFAULT_SERVICE_VERSION)
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .args(args)
                .build();
        //发送请求
        try {
            // 从注册中心获取服务提供者请求地址
            Registry registry = RegistryFactory.getInstance(rpcConfig.getRegistryConfig().getRegistry());
            ServiceMetaInfo serviceMetaInfo = ServiceMetaInfo.builder()
                    .serviceName(serviceName)
                    .serviceVersion(RpcConstant.DEFAULT_SERVICE_VERSION)
                    .build();
            List<ServiceMetaInfo> serviceMetaInfoList  = registry.getService(serviceMetaInfo.getServiceKey());
            if (CollUtil.isEmpty(serviceMetaInfoList)) {
                log.error("没有发现服务：{}", serviceMetaInfo.getServiceKey());
                throw new RuntimeException("暂无服务地址");
            }

            //负载均衡
            LoadBalancer loadBalancer = LoadBalancerFactory.getInstance(rpcConfig.getLoadBalancer());
            // 将调用方法名（请求路径）作为负载均衡参数
            // 因为这样调用相同方法的请求 Hash 值肯定相⁢同，所以总会请求到同一个服务器节点上
            // 也可以使用客户端的 IP 地址作为负载均衡参数
            HashMap<String, Object> map = new HashMap<>();
            map.put("methodName",rpcRequest.getMethodName());
            ServiceMetaInfo selectedServiceMetaInfo = loadBalancer.select(map, serviceMetaInfoList);
            log.info("{} 服务共有 {} 个节点, 当前访问的服务节点地址为 {}",
                    serviceName, serviceMetaInfoList.size(), selectedServiceMetaInfo.getServiceAddress());

            RpcResponse rpcResponse;
            //重试策略
            try {
                RetryStrategy retryStrategy = RetryStrategyFactory.getInstance(rpcConfig.getRetryStrategy());
                rpcResponse = retryStrategy.doRetry(() ->
                        VertxTcpClient.doRequest(rpcRequest, selectedServiceMetaInfo, serializerEnum));
            } catch (Exception e) {
                TolerantStrategy tolerantStrategy = TolerantStrategyFactory.getInstance(rpcConfig.getTolerantStrategy());
                rpcResponse = tolerantStrategy.doTolerant(null, e);
            }
            return rpcResponse.getData();
        } catch (Exception e) {
            log.error("服务调用失败, rpcRequest is {}", rpcRequest, e);
        }
        return null;
    }
}
